/***
* ==++==
*
* Copyright (c) Microsoft Corporation.  All rights reserved.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* uri.h
*
* Protocol independent support for URIs.
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/

#pragma once

#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "xxpublic.h"
#include "asyncrt_utils.h"

namespace http
{

class uri_builder;
namespace details
{
    class _uri;
} // namespace details

    /// <summary>
    /// A flexible, protocol independent uri implementation.
    ///
    /// URI instances are immutable.Querying the various fields on an emtpy uri will return empty strings. Querying
    /// various diagnostic members on an empty uri will return false.
    /// </summary>
    /// <remarks>
    /// This implementation accepts both uris ('http://msn.com/foo') and uri relative-references
    /// ('/foo?bar#baz').
    ///
    /// This implementation does not provide any scheme-specific handling -- an example of this
    /// would be the following: 'http:///foo/bar'. This is a valid uri, but it's not a valid 
    /// http-uri -- that is, it's syntactically correct but does not conform to the requirements 
    /// of the http scheme (http requires a host). 
    /// We could provide this by allowing a pluggable 'scheme' policy-class, which would provide 
    /// extra capability for validating and canonicalizing a uri according to scheme, and would 
    /// introduce a layer of type-safety for uris of differing schemes, and thus differing semantics.
    ///
    /// One issue with implementing a scheme-independent uri facility is that of comparing for equality.
    /// For instance, these uris are considered equal 'http://msn.com', 'http://msn.com:80'. That is --
    /// the 'default' port can be either omitted or explicit. Since we don't have a way to map a scheme
    /// to it's default port, we don't have a way to know these are equal. This is just one of a class of
    /// issues with regard to scheme-specific behavior.
    /// </remarks>
    class uri
    {
    public:

        /// </summary>
        /// Wrapper for a uri-encoded string. Use uri::encode to actually encode text. 
        /// Use uri::decode to extract the original text.
        /// </summary>
        class encoded_string
        {
        public:

            /// <summary>
            /// Creates a wrapper for an empty encoded string.
            /// </summary>
            encoded_string() : m_encoded(L"") {}

            /// </summary>
            /// Creates a wrapper for an already-encoded string. Use uri::encode to actually encode text.
            /// </summary>
            _ASYNCRTIMP explicit encoded_string(const std::string &raw);

            /// </summary>
            /// Creates a wrapper for an already-encoded string. Use uri::encode to actually encode text.
            /// </summary>
            _ASYNCRTIMP explicit encoded_string(const std::wstring &raw);

            /// </summary>
            /// Returns a string containing the encoded text.
            /// </summary>
            const std::string to_string() const
            {
                return utilities::conversions::utf16_to_utf8(this->m_encoded);
            }

			/// </summary>
            /// Returns a string containing the encoded text.
            /// </summary>
            const std::wstring& to_wstring() const
            {
                return this->m_encoded;
            }

            /// </summary>
            /// Compares two encoded_strings for equality
            /// </summary>
            bool operator==(const encoded_string &other) const
            {
                return this->m_encoded == other.m_encoded;
            }

            /// </summary>
            /// Compares two encoded_strings for inequality
            /// </summary>
            bool operator!=(const encoded_string &other) const
            {
                return !(*this == other);
            }

            /// <summary>
            /// Less than operator for encoded_strings. Required so can be stored in a map.
            /// </summary>
            bool operator<(const encoded_string &other) const
            {
                return m_encoded < other.m_encoded;
            }

        private:
            friend class uri;
            friend class details::_uri;
            friend class uri_builder;

            std::wstring m_encoded;
        };

        #pragma region encoding

        /// <summary>
        /// The various components of a URI. This enum is used to indicate which
        /// URI component is being encoded to the encode_uri_component. This allows
        /// specific encoding to be performed.
        ///
        /// Scheme and port don't allow '%' so they don't need to be encoded.
        /// </summary>
        class components
        {
        public:
            enum component
            {
                user_info,
                host,
                path,
                query,
                fragment,
                full_uri
            };
        };

        /// </summary>
        /// Encodes a string containing a complete URI according to RFC 3986. 
        /// Note since the URI is being encoded as a whole string and not individual components
        /// the reserved characters can't be encoded.
        /// </summary>
        /// <param name="raw">The URI as a string.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_uri(const std::string &raw);

        /// <summary>
        /// Encodes a URI component according to RFC 3986.
        /// Note if a full URI is specified instead of an individual URI component all 
        /// characters not in the unreserved set are escaped.
        /// </summary>
        /// <param name="raw">The URI as a string.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_uri_component(const std::string &raw, uri::components::component = components::full_uri);
        
        /// </summary>
        /// Encodes a string containing a complete URI according to RFC 3986. 
        /// Note since the URI is being encoded as a whole string and not individual components
        /// the reserved characters can't be encoded.
        /// </summary>
        /// <param name="raw">The URI as a string.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_uri(const std::wstring &raw);

        /// <summary>
        /// Encodes a URI component according to RFC 3986.
        /// Note if a full URI is specified instead of an individual URI component all 
        /// characters not in the unreserved set are escaped.
        /// </summary>
        /// <param name="raw">The URI as a string.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_uri_component(const std::wstring &raw, uri::components::component = components::full_uri);

        /// </summary>
        /// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their 
        /// hexadecimal representation.
        /// </summary>
        /// <param name="raw">The string data.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_data_string(const std::string &data);

        /// </summary>
        /// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their 
        /// hexadecimal representation.
        /// </summary>
        /// <param name="raw">The string data.</param>
        /// <returns>The encoded string.</returns>
        _ASYNCRTIMP static encoded_string __cdecl encode_data_string(const std::wstring &data);

        /// </summary>
        /// Decodes an encoded string
        /// </summary>
        /// <param name="encoded">The URI as a string.</param>
        /// <returns>The decoded string.</returns>
        _ASYNCRTIMP static std::string __cdecl decode(const encoded_string &encoded);

        /// </summary>
        /// Decodes an encoded string
        /// </summary>
        /// <param name="encoded">The URI as a string.</param>
        /// <returns>The decoded string.</returns>
        _ASYNCRTIMP static std::wstring __cdecl wdecode(const encoded_string &encoded);

        #pragma endregion

        #pragma region splitting

        /// </summary>
        /// Splits a path into its heirarchical components
        /// </summary>
        _ASYNCRTIMP static std::vector<std::string> __cdecl split_path(const std::string &path);

        /// </summary>
        /// Splits a path into its heirarchical components
        /// </summary>
        _ASYNCRTIMP static std::vector<std::wstring> __cdecl split_path(const std::wstring &path);

        /// </summary>
        /// Splits a path into its heirarchical components
        /// </summary>
        _ASYNCRTIMP static std::vector<uri::encoded_string> __cdecl split_path(const uri::encoded_string &path);

        /// </summary>
        /// Splits a path into its heirarchical components and decodes them
        /// </summary>
        static std::vector<std::string> split_path_and_decode(const uri::encoded_string &path)
        {
            return split_path(uri::decode(path));
        }

        /// </summary>
        /// Splits a path into its heirarchical components and decodes them
        /// </summary>
        static std::vector<std::wstring> split_path_and_wdecode(const uri::encoded_string &path)
        {
            return split_path(uri::wdecode(path));
        }

        /// </summary>
        /// Splits a query into its key-value components
        /// </summary>
        _ASYNCRTIMP static std::map<std::string, std::string> __cdecl split_query(const std::string &query);

        /// </summary>
        /// Splits a query into its key-value components
        /// </summary>
        _ASYNCRTIMP static std::map<std::wstring, std::wstring> __cdecl split_query(const std::wstring &query);

        /// </summary>
        /// Splits a query into its key-value components
        /// </summary>
        _ASYNCRTIMP static std::map<uri::encoded_string, uri::encoded_string> __cdecl split_query(const uri::encoded_string &query);

        /// </summary>
        /// Splits a query into its key-value components and decodes them
        /// </summary>
        static std::map<std::string, std::string> split_query_and_decode(const uri::encoded_string &query)
        {
            return split_query(uri::decode(query));
        }

        /// </summary>
        /// Splits a query into its key-value components and decodes them
        /// </summary>
        static std::map<std::wstring, std::wstring> split_query_and_wdecode(const uri::encoded_string &query)
        {
            return split_query(uri::wdecode(query));
        }

        #pragma endregion

        #pragma region validation

        /// <summary>
        /// Validates a string as a uri.
        /// </summary>
        _ASYNCRTIMP static bool __cdecl validate(const std::wstring &uri_string);
        _ASYNCRTIMP static bool __cdecl validate(const encoded_string &uri_string);

        #pragma endregion

        #pragma region constructors

        /// <summary>
        /// Creates an empty uri
        /// </summary>
        _ASYNCRTIMP uri();

        /// <summary>
        /// Creates a uri from the given encoded string. This will throw an exception if the string 
        /// does not contain a valid uri. Use uri::validate if processing user-input.
        /// </summary>
        _ASYNCRTIMP uri(const char *uri_string);

        /// <summary>
        /// Creates a uri from the given encoded string. This will throw an exception if the string 
        /// does not contain a valid uri. Use uri::validate if processing user-input.
        /// </summary>
        _ASYNCRTIMP uri(const wchar_t *uri_string);

        /// <summary>
        /// Creates a uri from the given encoded string. This will throw an exception if the string 
        /// does not contain a valid uri. Use uri::validate if processing user-input.
        /// </summary>
        _ASYNCRTIMP uri(const std::string &uri_string);

        /// <summary>
        /// Creates a uri from the given encoded string. This will throw an exception if the string 
        /// does not contain a valid uri. Use uri::validate if processing user-input.
        /// </summary>
        _ASYNCRTIMP uri(const std::wstring &uri_string);

        /// <summary>
        /// Creates a uri from the given encoded string. This will throw an exception if the string 
        /// does not contain a valid uri. Use uri::validate if processing user-input.
        /// </summary>
        _ASYNCRTIMP uri(const encoded_string &uri_string);

        /// <summary>
        /// Copy constructor.
        /// </summary>
        uri(const uri &other) : m_uri(other.m_uri) {}

        #pragma endregion

        #pragma region accessors

        /// <summary>
        /// Gets a uri with the same authority portion as this uri (clears resource and query)
        /// </summary>
        _ASYNCRTIMP uri authority() const;

        /// <summary>
        /// Gets the scheme portion of this uri, which may be empty
        /// </summary>
        const std::wstring &scheme() const;

        /// <summary>
        /// Gets the host portion of this uri, which may be empty
        /// </summary>
        const uri::encoded_string &host() const;

        /// <summary>
        /// Gets the host portion of this uri, which may be empty
        /// </summary>
        const uri::encoded_string &user_info() const;

        /// <summary>
        /// Gets the port portion of this uri, which may be the default value
        /// of '0'
        /// </summary>
        const int port() const;

        /// <summary>
        /// <summary>
        /// Gets the port portion of this uri, which may be empty
        /// </summary>
        _ASYNCRTIMP const std::wstring port_string() const;

        /// <summary>
        /// Gets the path portion of this uri, as an encoded string, which may be empty
        /// </summary>
        const encoded_string &path() const;

        /// <summary>
        /// Gets the query portion of this uri, which may be empty
        /// </summary>
        const uri::encoded_string &query() const;

        /// <summary>
        /// Gets the fragment portion of this uri, which may be empty
        /// </summary>
        const uri::encoded_string &fragment() const;

        /// <summary>
        /// Gets the path, query, and fragment portion of this uri, which may be empty.
        /// </summary>
        _ASYNCRTIMP uri resource() const;

        #pragma endregion

        #pragma region diagnostics

        /// <summary>
        /// An empty uri specifies no components, and serves as a default value
        /// </summary>
        _ASYNCRTIMP bool empty() const;

        /// <summary>
        /// A loopback URI is one which refers to a hostname or ip address with meaning only on the local machine
        /// </summary>
        /// <remarks>
        /// Examples include "locahost", or ip addresses in the loopback range (127.0.0.0/24)
        /// </remarks>
        _ASYNCRTIMP bool is_host_loopback() const;

        /// <summary>
        /// A wildcard URI is one which refers to all hostnames that resolve to the local machine (using the * or +)
        /// </summary>
        /// <example>
        /// http://*:80
        /// </example>
        _ASYNCRTIMP bool is_host_wildcard() const;

        /// <summary>
        /// A portable URI is one with a hostname that can be resolved globally (used from another machine).
        /// </summary>
        /// <remarks>
        /// The hostname "localhost" is a reserved name that is guaranteed to resolve to the local machine,
        /// and cannot be used for inter-machine communication. Likewise the hostnames "*" and "+" on Windows
        /// represent wildcards, and do not map to a resolvable address.
        /// </remarks>
        _ASYNCRTIMP bool is_host_portable() const;

        // <summary>
        /// A default port is one where the port is unspecified, and will be determined by the operating system.
        /// The choice of default port may be dictated by the scheme (http -> 80) or not.
        /// </summary>
        _ASYNCRTIMP bool is_port_default() const;

        /// <summary>
        /// An "authority" uri is one with only a scheme, optional userinfo, hostname, and (optional) port.
        /// </summary>
        _ASYNCRTIMP bool is_authority() const;

        /// <summary>
        /// Returns whether the other uri has the same authority as this one
        /// </summary>
        _ASYNCRTIMP bool has_same_authority(const uri &other) const;

        /// <summary>
        /// Returns whether the path portion of this uri is empty
        /// </summary>
        _ASYNCRTIMP bool is_path_empty() const;

        #pragma endregion

    #pragma region combining

        /// <summary>
        /// Appends another uri's path component onto this one. All other portions of the uri are ignored.
        /// </summary>
        /// <param name="other">URI containing path to append.</param>
        /// <returns>A new URI.</returns>
        _ASYNCRTIMP uri append_path(const uri &other) const;

        /// <summary>
        /// Appends another uri's query component onto this one. All other portions of the uri are ignored.
        /// </summary>
        /// <param name="other">URI containing query to append.</param>
        /// <returns>A new URI.</returns>
        _ASYNCRTIMP uri append_query(const uri &other) const;

        /// <summary>
        /// Appends another uri's path, query, and fragment components onto this one. All other portions of the uri are ignored.
        /// </summary>
        /// <param name="other">URI containing path, query, and fragment to append.</param>
        /// <returns>A new URI.</returns>
        _ASYNCRTIMP uri append(const uri &other) const;

    #pragma endregion

        #pragma region conversions

        /// <summary>
        /// Returns the full (encoded) uri as a string
        /// </summary>
        _ASYNCRTIMP uri::encoded_string to_encoded_string() const;

        /// <summary>
        /// Returns the full (decoded) uri as a string
        /// </summary>
        _ASYNCRTIMP std::string to_string() const;

        /// <summary>
        /// Returns the full (decoded) uri as a string
        /// </summary>
        _ASYNCRTIMP std::wstring to_wstring() const;

        /// <summary>
        /// Returns the full (decoded) uri as a string
        /// </summary>
        operator std::string() const;

        /// <summary>
        /// Returns the full (decoded) uri as a string
        /// </summary>
        operator std::wstring() const;

        #pragma endregion

        #pragma region operators

        _ASYNCRTIMP bool operator== (const uri &other) const; 
        bool operator!= (const uri &other) const; 

        /// <summary>
        /// Assignment operator
        /// </summary>
        _ASYNCRTIMP uri &operator=(const uri &other);

        #pragma endregion

    private:
        friend class uri_builder;

        /// <summary>
        /// Private constructor.
        /// </summary>
        explicit uri(std::shared_ptr<details::_uri> other) : m_uri(other) { }

        // Encodes all characters not in given set determined by given function.
        template <typename T>
        static uri::encoded_string encode_impl(const std::wstring &raw, T should_encode)
        {
            const std::wstring hex = L"0123456789ABCDEF";
            uri::encoded_string encoded;
            for(auto iter = raw.begin(); iter != raw.end(); ++iter)
            {
                const wchar_t ch = *iter;
                if(should_encode(ch))
                {
                    encoded.m_encoded.push_back(L'%');
                    encoded.m_encoded.push_back(hex[(ch >> 4) & 0xF]);
                    encoded.m_encoded.push_back(hex[ch & 0xF]);
                }
                else
                {
                    encoded.m_encoded.push_back(ch);
                }
            }
            return encoded;
        }

        std::shared_ptr<details::_uri> m_uri;
    };

    namespace details
    {
        /// <summary>
        /// Implementation details of uri
        /// </summary>
        class _uri
        {
        public:

            /// <summary>
            /// Empty constructor - creates an empty URI.
            /// </summary>
            _uri() : m_port(-1), m_path(uri::encoded_string(L"/"))
            {
            }

            /// <summary>
            /// Splitting constructor -- this accepts a (validated, encoded) uri and initializes the component
            /// fields.
            /// </summary>
            _uri(const uri::encoded_string &full_uri) : m_full_uri(full_uri)
            {
                split();
                canonicalize();

                // call join() to populate the full uri after we've canonicalized
                join();
            }

            /// <summary>
            /// Splitting constructor -- this accepts (encoded) components of a uri and initializes the full uri.
            /// </summary>
            _uri(const std::wstring &scheme, const uri::encoded_string &host, int port, const uri::encoded_string &path, const uri::encoded_string &query, const uri::encoded_string &fragment)
                : m_scheme(scheme), m_host(host), m_port(port), m_path(path), m_query(query), m_fragment(fragment)
            { 
                canonicalize();
                join();
            }

            /// <summary>
            /// Copy constructor
            /// </summary>
            _uri(const _uri &other)
                : m_full_uri(other.m_full_uri),
                m_scheme(other.m_scheme),
                m_user_info(other.m_user_info),
                m_host(other.m_host),
                m_port(other.m_port),
                m_path(other.m_path),
                m_query(other.m_query),
                m_fragment(other.m_fragment)
            {
            }

            /// <summary>
            /// Move constructor
            /// </summary>
            _uri(_uri &&other) 
                : m_full_uri(std::move(other.m_full_uri)),
                m_scheme(std::move(other.m_scheme)),
                m_user_info(std::move(other.m_user_info)),
                m_host(std::move(other.m_host)),
                m_port(other.m_port),
                m_path(std::move(other.m_path)),
                m_query(std::move(other.m_query)),
                m_fragment(std::move(other.m_fragment))
            {
            }

            /// <summary>
            /// Splits the string held in m_full_uri into its component pieces, and stores them 
            /// in the component fields in this instance.
            /// </summary>
            void split();

            /// <summary>
            /// Canonicalizes each component
            /// </summary>
            void canonicalize();

            /// <summary>
            /// Populates m_full_uri from the component pieces and validates the uri.
            /// </summary>
            void join();

        private:
            friend class http::uri;
            friend class http::uri_builder;

            /// <summary>
            /// Holds the full uri
            /// </summary>
            uri::encoded_string m_full_uri;

            std::wstring m_scheme;
            uri::encoded_string m_host;
            uri::encoded_string m_user_info;
            int m_port;
            uri::encoded_string m_path;
            uri::encoded_string m_query;
            uri::encoded_string m_fragment;
        };

    } // namespace details

    // Definitions for some of the simple uri class functions.
    inline const std::wstring &uri::scheme() const { return this->m_uri->m_scheme; }
    inline const uri::encoded_string &uri::host() const { return this->m_uri->m_host; }
    inline const uri::encoded_string &uri::user_info() const { return this->m_uri->m_user_info; }
    inline const int uri::port() const { return this->m_uri->m_port; }
    inline const uri::encoded_string &uri::path() const { return this->m_uri->m_path; }
    inline const uri::encoded_string &uri::query() const { return this->m_uri->m_query; }
    inline const uri::encoded_string &uri::fragment() const { return this->m_uri->m_fragment; }
    inline uri::operator std::string() const { return this->to_string(); }
    inline uri::operator std::wstring() const { return this->to_wstring(); }
    inline bool uri::operator!= (const uri &other) const { return !(*this == other); }

    /// <summary>
    /// Builder for constructing URIs incrementally.
    /// </summary>
    class uri_builder
    {
    public:

    #pragma region Constructors

        /// <summary>
        /// Creates a builder with an initially empty URI.
        /// </summary>
        uri_builder() : m_uri() {}

        /// <summary>
        /// Creates a builder with with a URI containing the given scheme, unencoded host, and port.
        /// </summary>
        /// <param name="scheme">String containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        _ASYNCRTIMP uri_builder(const std::string &scheme, const std::string &host, const int port);

        /// <summary>
        /// Creates a builder with with a URI containing the given scheme, encoded host, and port.
        /// </summary>
        /// <param name="scheme">String containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        _ASYNCRTIMP uri_builder(const std::string &scheme, const uri::encoded_string &host, const int port);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, encoded host, port, and encoded path.
        /// </summary>
        /// <param name="scheme">Encoded string containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        /// <param name="path">Encoded string containing the URI path.</param>
        _ASYNCRTIMP uri_builder(const std::string &scheme, const uri::encoded_string &host, const int port, const uri::encoded_string &path);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, unencoded host, port, and unencoded path.
        /// </summary>
        /// <param name="scheme">Encoded string containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        /// <param name="path">Encoded string containing the URI path.</param>
        _ASYNCRTIMP uri_builder(const std::string &scheme, const std::string &host, const int port, const std::string &path);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, unencoded host, and port.
        /// </summary>
        /// <param name="scheme">String containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        _ASYNCRTIMP uri_builder(const std::wstring &scheme, const std::wstring &host, const int port);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, encoded host, and port.
        /// </summary>
        /// <param name="scheme">String containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        _ASYNCRTIMP uri_builder(const std::wstring &scheme, const uri::encoded_string &host, const int port);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, unencoded host, port, and unencoded path.
        /// </summary>
        /// <param name="scheme">Encoded string containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        /// <param name="path">Encoded string containing the URI path.</param>
        _ASYNCRTIMP uri_builder(const std::wstring &scheme, const std::wstring &host, const int port, const std::wstring &path);

		/// <summary>
        /// Creates a builder with with a URI containing the given scheme, encoded host, port, and encoded path.
        /// </summary>
        /// <param name="scheme">Encoded string containing the URI scheme.</param>
        /// <param name="host">Encoded string containing the URI host.</param>
        /// <param name="port">Integer containing the URI port.</param>
        /// <param name="path">Encoded string containing the URI path.</param>
        _ASYNCRTIMP uri_builder(const std::wstring &scheme, const uri::encoded_string &host, const int port, const uri::encoded_string &path);

		/// <summary>
        /// Creates a builder with an encoded string containing the intial URI.
        /// Throws an exception if the uri_str is an invalid URI.
        /// </summary>
        /// <param name="uri_str">Encoded string containing the URI.<param>
        uri_builder(const uri &uri_str) : m_uri(*uri_str.m_uri) {}

        /// <summary>
        /// Copy constructor.
        /// </summary>
        uri_builder(const uri_builder &other) : m_uri(other.m_uri) {}

        /// <summary>
        /// Move constructor.
        /// </summary>
        uri_builder(uri_builder &&other) : m_uri(std::move(other.m_uri)) {}

        /// <summary>
        /// Assignment operator.
        /// </summary>
        _ASYNCRTIMP uri_builder &operator=(const uri_builder &other);

        /// <summary>
        /// Move assignment operator.
        /// </summary>
        _ASYNCRTIMP uri_builder &operator=(uri_builder &&other);

    #pragma endregion

    #pragma region Accessors

        /// <summary>
        /// Get the scheme component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI scheme as a string.</returns>
        const std::wstring &scheme() const { return m_uri.m_scheme; }

        /// <summary>
        /// Get the user information component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI user information as a string.</returns>
        const uri::encoded_string &user_info() const { return m_uri.m_user_info; }

        /// <summary>
        /// Get the host component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI host as a string.</returns>
        const uri::encoded_string &host() const { return m_uri.m_host; }

        /// <summary>
        /// Get the port component of the URI. Returns -1 if no port is specified.
        /// </summary>
        /// <returns>The URI port as an integer.</returns>
        int port() const { return m_uri.m_port; }

        /// <summary>
        /// Get the path component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI path as a string.</returns>
        const uri::encoded_string &path() const { return m_uri.m_path; }

        /// <summary>
        /// Get the query component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI query as a string.</returns>
        const uri::encoded_string &query() const { return m_uri.m_query; }

        /// <summary>
        /// Get the fragment component of the URI as an encoded string.
        /// </summary>
        /// <returns>The URI fragment as a string.</returns>
        const uri::encoded_string &fragment() const { return m_uri.m_fragment; }

        /// <summary>
        /// Gets the path, query, and fragment of the URI as a new URI.
        /// </summary>
        _ASYNCRTIMP uri resource() const;

    #pragma endregion

    #pragma region Modifiers

        /// <summary>
        /// Set the scheme component of the URI.
        /// Note the scheme doesn't need to be encoded because '%' isn't allowed.
        /// </summary>
        /// <param name="scheme">Scheme as a string.</param>
        uri_builder &set_scheme(const std::string &scheme) 
        { 
            m_uri.m_scheme = utilities::conversions::utf8_to_utf16(scheme); 
            return *this; 
        }
        /// <summary>
        /// Set the scheme component of the URI.
        /// Note the scheme doesn't need to be encoded because '%' isn't allowed.
        /// </summary>
        /// <param name="scheme">Scheme as a string.</param>
        uri_builder &set_scheme(const std::wstring &scheme) 
        { 
            m_uri.m_scheme = scheme; 
            return *this; 
        }

        /// <summary>
        /// Set the host component of the URI, encoding it first.
        /// </summary>
        /// <param name="host">Host as a decoded string.</param>
        uri_builder & set_host(const std::string &host) 
        { 
            m_uri.m_host = uri::encode_uri_component(host, uri::components::host);
            return *this;
        }

        /// <summary>
        /// Set the host component of the URI, encoding it first.
        /// </summary>
        /// <param name="host">Host as a decoded string.</param>
        uri_builder & set_host(const std::wstring &host) 
        { 
            m_uri.m_host = uri::encode_uri_component(host, uri::components::host);
            return *this;
        }

		/// <summary>
        /// Set the host component of the URI.
        /// </summary>
        /// <param name="host">Host as an encoded string.</param>
        uri_builder & set_host(const uri::encoded_string &host) 
        { 
            m_uri.m_host = host; 
            return *this;
        }

        /// <summary>
        /// Set the port component of the URI.
        /// </summary>
        /// <param name="port">Port as an integer.</param>
        uri_builder & set_port(int port) 
        { 
            m_uri.m_port = port; 
            return *this;
        }

        /// <summary>
        /// Set the path component of the URI, encoding it first.
        /// </summary>
        /// <param name="path">Path as an decoded string.</param>
        uri_builder & set_path(const std::string &path) 
        { 
            m_uri.m_path = uri::encode_uri_component(path, uri::components::path); 
            return *this;
        }

        /// <summary>
        /// Set the path component of the URI, encoding it first.
        /// </summary>
        /// <param name="path">Path as an decoded string.</param>
        uri_builder & set_path(const std::wstring &path) 
        { 
            m_uri.m_path = uri::encode_uri_component(path, uri::components::path); 
            return *this;
        }

        /// <summary>
        /// Set the path component of the URI.
        /// </summary>
        /// <param name="path">Path as an encoded string.</param>
        uri_builder & set_path(const uri::encoded_string &path) 
        { 
            m_uri.m_path = path; 
            return *this;
        }

        /// <summary>
        /// Set the query component of the URI, encoding it first.
        /// </summary>
        /// <param name="query">Query as an decoded string.</param>
        uri_builder & set_query(const std::string &query) 
        { 
            m_uri.m_query = uri::encode_uri_component(query, uri::components::query);
            return *this;
        }

        /// <summary>
        /// Set the query component of the URI, encoding it first.
        /// </summary>
        /// <param name="query">Query as an decoded string.</param>
        uri_builder & set_query(const std::wstring &query) 
        { 
            m_uri.m_query = uri::encode_uri_component(query, uri::components::query);
            return *this;
        }

        /// <summary>
        /// Set the query component of the URI.
        /// </summary>
        /// <param name="query">Query as an encoded string.</param>
        uri_builder & set_query(const uri::encoded_string &query) 
        { 
            m_uri.m_query = query;
            return *this;
        }

        /// <summary>
        /// Set the fragment component of the URI, encoding it first.
        /// </summary>
        /// <param name="fragment">Fragment as an decoded string.</param>
        uri_builder & set_fragment(const std::string &fragment) 
        { 
            m_uri.m_fragment = uri::encode_uri_component(fragment, uri::components::fragment);
            return *this;
        }

        /// <summary>
        /// Set the fragment component of the URI, encoding it first.
        /// </summary>
        /// <param name="fragment">Fragment as an decoded string.</param>
        uri_builder & set_fragment(const std::wstring &fragment) 
        { 
            m_uri.m_fragment = uri::encode_uri_component(fragment, uri::components::fragment);
            return *this;
        }

        /// <summary>
        /// Set the fragment component of the URI.
        /// </summary>
        /// <param name="fragment">Fragment as an encoded string.</param>
        uri_builder & set_fragment(const uri::encoded_string &fragment) 
        { 
            m_uri.m_fragment = fragment;
            return *this;
        }

    #pragma endregion

#pragma region Appending

        /// <summary>
        /// Appends another path to the path of this uri_builder, encoding it first.
        /// </summary>
        /// <param name="path">Path to append as a decoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        uri_builder &append_path(const std::string &path) { return append_path(uri::encode_uri_component(path, uri::components::path)); }

        /// <summary>
        /// Appends another path to the path of this uri_builder, encoding it first.
        /// </summary>
        /// <param name="path">Path to append as a decoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        uri_builder &append_path(const std::wstring &path) { return append_path(uri::encode_uri_component(path, uri::components::path)); }

		/// <summary>
        /// Appends another path to the path of this uri_builder.
        /// </summary>
        /// <param name="path">Path to append as a already encoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        _ASYNCRTIMP uri_builder &append_path(const uri::encoded_string &encoded_path);

        /// <summary>
        /// Appends another query to the query of this uri_builder, encoding it first.
        /// </summary>
        /// <param name="query">Query to append as a decoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        uri_builder &append_query(const std::string &query) { return append_query(uri::encode_uri_component(query, uri::components::query)); }

        /// <summary>
        /// Appends another query to the query of this uri_builder, encoding it first.
        /// </summary>
        /// <param name="query">Query to append as a decoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        uri_builder &append_query(const std::wstring &query) { return append_query(uri::encode_uri_component(query, uri::components::query)); }

        /// <summary>
        /// Appends another query to the query of this uri_builder, encoding it first.
        /// </summary>
        /// <param name="query">Query to append as a already encoded string.</param>
        /// <returns>A reference to this uri_builder to support chaining.</returns>
        _ASYNCRTIMP uri_builder &append_query(const uri::encoded_string &encoded_query);

        /// <summary>
        /// Appends another path, query, fragment string to this uri_builder, encoding it first.
        /// </summary>
        /// <param name="path_query_fragment">Path, query, and fragment to append as a decoded string.</param>
        uri_builder &append(const std::string &path_query_fragment) { return append(uri::encode_uri(path_query_fragment)); }

        /// <summary>
        /// Appends another path, query, fragment string to this uri_builder, encoding it first.
        /// </summary>
        /// <param name="path_query_fragment">Path, query, and fragment to append as a decoded string.</param>
        uri_builder &append(const std::wstring &path_query_fragment) { return append(uri::encode_uri(path_query_fragment)); }

        /// <summary>
        /// Appends another path, query, fragment string to this uri_builder, encoding it first.
        /// </summary>
        /// <param name="path_query_fragment">Path, query, and fragment to append as a already encoded string.</param>
        _ASYNCRTIMP uri_builder &append(const uri::encoded_string &path_query_fragment);

#pragma endregion

    #pragma region Validation

        /// <summary>
        /// Determines whether or not the URI builder currently contains a valid URI.
        /// </summary>
        /// <returns>True if URI is valid, false otherwise.</returns>
        _ASYNCRTIMP bool is_valid();

    #pragma endregion

    #pragma region URI Creation

        /// <summary>
        /// Combine and validate the URI components into a decoded string. An exception will be thrown if the URI is invalid.
        /// </summary>
        /// <returns>The created URI as a string.</returns>
        _ASYNCRTIMP std::string to_string();

        /// <summary>
        /// Combine and validate the URI components into a decoded string. An exception will be thrown if the URI is invalid.
        /// </summary>
        /// <returns>The created URI as a string.</returns>
        _ASYNCRTIMP std::wstring to_wstring();

        /// <summary>
        /// Combine and validate the URI components into an encode string. An exception will be thrown if the URI is invalid.
        /// </summary>
        /// <returns>The created URI as an encoded string.</returns>
        _ASYNCRTIMP http::uri::encoded_string to_encoded_string();

        /// <summary>
        /// Combine and validate the URI components into a URI class instance. An exception will be thrown if the URI is invalid.
        /// </summary>
        /// <returns>The create URI as a URI class instance.</returns>
        _ASYNCRTIMP uri to_uri();

    #pragma endregion

    private:

        // Helper function to check if current URI is valid and if not throw an exception.
        void check_if_valid();

        http::details::_uri m_uri;
    };

} // namespace http
